'''
Created on 2009/05/24

@author: coLun
'''

from google.appengine.ext import db

from logic import *
from html import *
from datetime import *

class EntityCounter(db.Model):
    count = db.IntegerProperty()
    @staticmethod
    def inc(name, default = 0, amount = 1):
        key = 'c<' + name + '>'
        def txn():
            o = EntityCounter.get_by_key_name(key)
            if o==None:
                o = EntityCounter(key_name = key, count = default)
            o.count += amount
            o.put()
            return o.count
        return db.run_in_transaction(txn)
    @staticmethod
    def set(name, value = 0):
        key = 'c<' + name + '>'
        def txn():
            o = EntityCounter.get_by_key_name(key)
            if o==None:
                o = EntityCounter(key_name = key)
            o.count = value
            o.put()
            return o.count
        return db.run_in_transaction(txn)
    @staticmethod
    def get(name, default = 0):
        key = 'c<' + name + '>'
        o = EntityCounter.get_by_key_name(key)
        if o==None:
            return default
        return o.count

class DerefModel(db.Model):
    def get_key(self, prop_name):
        return getattr(self.__class__, prop_name).get_value_for_datastore(self)

class EntityPool:
    pool = {}
    req = {}
    def add(self, key):
        if key!=None and not self.pool.has_key(key):
            self.req[key] = 1
    def __getitem__(self, key):
        if key==None:
            return None
        if self.pool.has_key(key):
            return self.pool[key]
        if self.req.has_key(key):
            key_list = self.req.keys()
            self.req = {}
            obj_list = db.get(key_list)
            for i in range(len(key_list)):
                self.pool[key_list[i]] = obj_list[i]
            return self.pool[key]
        raise ValueError()

class CsvSetProperty(db.Property):
    data_type = list
    def get_value_for_datastore(self, model_instance):
        csvArray = super(CsvSetProperty, self).get_value_for_datastore(model_instance)
        return db.Text(Trans.CsvArraySerialize(csvArray))
    def make_value_from_datastore(self, value):
        if value is None:
            return None
        return Trans.CsvArrayDeserialize(value)
    def validate(self, value):
        if value is not None and not isinstance(value, list):
            raise db.BadValueError('Property %s must be convertible '
                          'to a list instance (%s)' %
                          (self.name, value))
        return super(CsvSetProperty, self).validate(value)
    def empty(self, value):
        return not value

class ProblemSet(DerefModel):
    problemId = db.IntegerProperty()
    subject = db.StringProperty()
    csvSet = CsvSetProperty()
    comment = db.TextProperty()
    author = db.ReferenceProperty(User)
    holder = db.ReferenceProperty()
    CDate = db.DateTimeProperty(auto_now_add = True)
    LDate = db.DateTimeProperty(auto_now = True)
    @staticmethod
    def New(subject, csv_array, user, comment):
        for csv in csv_array:
            chkProblem = Trans.CsvToProblem(csv)
        id = EntityCounter.inc("Problem", default = 100)
        key = 'p<' + str(id) + '>'
        p = ProblemSet(key_name = key)
        p.problemId = id
        p.subject = subject
        p.csvSet = csv_array
        p.author = user
        p.comment = db.Text(comment)
        p.put()
        return id
    @staticmethod
    def Get(id):
        key = 'p<' + str(id) + '>'
        return ProblemSet.get_by_key_name(key)
    def getProblemCsvCount(self):
        return len(self.csvSet)
    def getRanking(self):
        count = self.getProblemCsvCount()

        entries = []
        for entry in self.rankingEntries:
            entries.append(entry)
        if len(entries)==0:
            return None
        for entry in entries:
            entry.rank = []
        for i in range(count):
            def my_cmp(a, b):
                return (
                        cmp(b.scoreList[i], a.scoreList[i])
                     or cmp(a.opeCountList[i], b.opeCountList[i])
                        )
            entries.sort(my_cmp)
            rnk = 0
            k = 1
            before_entry = None
            for entry in entries:
                if before_entry!=None and my_cmp(before_entry, entry)==0:
                    k += 1
                else:
                    rnk += k
                    k = 1
                entry.rank.append(rnk)
                before_entry = entry
        for entry in entries:
            sum = 0
            for rnk in entry.rank:
                sum += rnk
            entry.rank.append(sum)
        entries.sort(RankingEntry.RankingCmp(count))
        return entries
    def renewRanking(self):
        entries = self.getRanking()
        if entries==None:
            self.holder = None
        else:
            self.holder = entries[0].user
        self.put()

class GameSession(db.Model):
    gameId = db.IntegerProperty()
    user = db.ReferenceProperty(User)
    problemSet = db.ReferenceProperty(ProblemSet)
    csvSet = CsvSetProperty()
    ansTimeSet = db.ListProperty(datetime, indexed = False)
    startDate = db.DateTimeProperty()
    finishDate = db.DateTimeProperty()
    CDate = db.DateTimeProperty(auto_now_add = True)
    LDate = db.DateTimeProperty(auto_now = True)
    @staticmethod
    def New(user, problemSet):
        csvSet = []
        for i in range(problemSet.getProblemCsvCount()):
            csvSet.append('')

        id = EntityCounter.inc("Game", default = 100)
        key = 'g<' + str(id) + '>'
        p = GameSession(key_name = key)
        p.gameId = id
        p.user = user
        p.problemSet = problemSet
        p.csvSet = csvSet
        p.startDate = None
        p.put()
        return id
    @staticmethod
    def GetNowUsersGame(id):
        game = GameSession.GetGame(id)
        if game!=None:
            guser = users.get_current_user()
            if guser!=None and game.user.email==guser.email():
                return game
        return None
    @staticmethod
    def GetGame(id):
        key = 'g<' + str(id) + '>'
        game = GameSession.get_by_key_name(key)
        return game
    def calcTime(self, idx):
        dt = self.ansTimeSet[idx] - self.startDate
        return dt.days*86400 + dt.seconds
    def calcScore(self, idx):
        problem = Trans.CsvToProblem(self.problemSet.csvSet[idx])
        answerCsv = self.csvSet[idx]
        ansOpeCount = 0
        if answerCsv!='':
            answer = Trans.CsvToAnswer(answerCsv)
            ansOpeCount = answer.getOpeCount()
            answer.action(problem)
        class MyResult:
            pass
        o = MyResult()
        o.point = problem.judge()
        o.opeCount = ansOpeCount
        return o
    def startGame(self):
        if self.startDate==None:
            now = datetime.now()
            ansTimeSet = []
            for i in range(self.getProblemCsvCount()):
                ansTimeSet.append(now)
            self.startDate = now
            self.ansTimeSet = ansTimeSet
            self.put()
    def finishGame(self):
        if self.finishDate==None:
            self.finishDate = datetime.now()
            self.put()
    def getProblemCsvCount(self):
        return self.problemSet.getProblemCsvCount()

class RankingEntry(DerefModel):
    entryId = db.IntegerProperty()
    user = db.ReferenceProperty(User)
    problemSet = db.ReferenceProperty(ProblemSet, collection_name = 'rankingEntries')
    ansCsvSet = CsvSetProperty()
    scoreList = db.ListProperty(long, indexed = False)
    opeCountList = db.ListProperty(long, indexed = False)
    secList = db.ListProperty(long, indexed = False)
    CDate = db.DateTimeProperty(auto_now_add = True)
    LDate = db.DateTimeProperty(auto_now = True)
    @staticmethod
    def RankingCmp(count):
        def my_cmp(a, b):
            ret = cmp(a.rank[count], b.rank[count])
            if ret:
                return ret
            for i in range(count):
                ret = cmp(a.rank[i], b.rank[i])
                if ret:
                    return ret
            for i in range(count):
                ret = cmp(a.secList[i], b.secList[i])
                if ret:
                    return ret
            return 0
        return my_cmp

    @staticmethod
    def Regist(gameSession):
        count = gameSession.getProblemCsvCount()
        problemId = gameSession.problemSet.problemId
        email = gameSession.user.email
        key = 'e<' + str(problemId) + ',' + email + '>'
        entryId = EntityCounter.inc("Ranking", default = 100)
        scoreList = []
        secList = []
        opeCountList = []
        for i in range(count):
            score = gameSession.calcScore(i)
            sec = gameSession.calcTime(i)
            scoreList.append(score.point)
            opeCountList.append(score.opeCount)
            secList.append(sec)
        def txn():
            entry = RankingEntry.get_by_key_name(key)
            if entry==None:
                entry = RankingEntry(key_name = key)
                entry.user = gameSession.user
                entry.problemSet = gameSession.problemSet
            entry.entryId = entryId
            entry.ansCsvSet = gameSession.csvSet
            entry.scoreList = scoreList
            entry.opeCountList = opeCountList
            entry.secList = secList
            entry.put()
        db.run_in_transaction(txn)
        gameSession.problemSet.renewRanking()
    @staticmethod
    def GetNowUsersEntry(user, problemId):
        email = user.email
        key = 'e<' + str(problemId) + ',' + email + '>'
        return RankingEntry.get_by_key_name(key)

class ProblemComment(DerefModel):
    commentId = db.IntegerProperty()
    problemSet = db.ReferenceProperty(ProblemSet)
    user = db.ReferenceProperty(User)
    comment = db.TextProperty()
    CDate = db.DateTimeProperty(auto_now_add = True)
    LDate = db.DateTimeProperty(auto_now = True)
    @staticmethod
    def say(problemSet, user, comment):
        commentId = EntityCounter.inc("problem%d-Comment" % (problemSet.problemId), default = 0)
        key = 'pc<%d,%d>' % (problemSet.problemId, commentId)
        problemComment = ProblemComment(key_name = key, problemSet = problemSet, user = user, comment = comment, commentId = commentId)
        problemComment.put()
    @staticmethod
    def GetNewlyComments(problemSet):
        result = []
        for c in ProblemComment.gql('where problemSet = :1 order by commentId desc limit 10', problemSet):
            result.append(c)
        result.reverse()
        return result
    @staticmethod
    def GetCommentsByPage(problemSet, page):
        result = []
        for c in ProblemComment.gql('where problemSet = :1 and commentId > :2 and commentId <= :3 order by commentId desc limit 100', problemSet, page*100-100, page*100):
            result.append(c)
        result.reverse()
        return result
    @staticmethod
    def GetCommentCount(problemSet):
        return EntityCounter.get("problem%d-Comment" % (problemSet.problemId), default = 0)
